package aurora.plugin.script.engine;

import java.lang.reflect.Member;
import java.lang.reflect.Modifier;

import aurora.javascript.ClassShutter;
import aurora.javascript.Context;
import aurora.javascript.NativeJavaObject;
import aurora.javascript.Scriptable;
import aurora.javascript.WrapFactory;

/**
 * This wrap factory is used for seimport static
 * sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
 * 
 * import java.lang.reflect.Member; import java.lang.reflect.Modifier;
 * 
 * import aurora.javascript.ClassShutter; import
 * aurora.javascript.Context; import
 * aurora.javascript.NativeJavaObject; import
 * aurora.javascript.Scriptable; import aurora.javascript.WrapFactory;
 * lang.Class.forName method (when called without class loader) uses caller's
 * class loader. This may be exploited by script authors to access classes
 * otherwise not accessible. For example, classes in sun.* namespace are
 * normally not accessible to untrusted code and hence should not be accessible
 * to JavaScript run from untrusted code.
 * 
 * @author A. Sundararajan
 * @since 1.6
 */
final class RhinoWrapFactory extends WrapFactory {
	private RhinoWrapFactory() {
	}

	private static RhinoWrapFactory theInstance;

	static synchronized WrapFactory getInstance() {
		if (theInstance == null) {
			theInstance = new RhinoWrapFactory();
		}
		return theInstance;
	}

	// We use instance of this class to wrap security sensitive
	// Java object. Please refer below.
	private static class RhinoJavaObject extends NativeJavaObject {
		/**
		 * 
		 */
		private static final long serialVersionUID = -626320966516911555L;

		RhinoJavaObject(Scriptable scope, Object obj, Class<?> type) {
			// we pass 'null' to object. NativeJavaObject uses
			// passed 'type' to reflect fields and methods when
			// object is null.
			super(scope, null, type);

			// Now, we set actual object. 'javaObject' is protected
			// field of NativeJavaObject.
			javaObject = obj;
		}
	}

	public Scriptable wrapAsJavaObject(Context cx, Scriptable scope,
			Object javaObject, Class<?> staticType) {
		SecurityManager sm = System.getSecurityManager();
		ClassShutter classShutter = RhinoClassShutter.getInstance();
		if (javaObject instanceof ClassLoader) {
			// Check with Security Manager whether we can expose a
			// ClassLoader...
			if (sm != null) {
				sm.checkPermission(sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION);
			}
			// if we fall through here, check permission succeeded.
			return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
		} else {
			String name = null;
			if (javaObject instanceof Class) {
				name = ((Class<?>) javaObject).getName();
			} else if (javaObject instanceof Member) {
				Member member = (Member) javaObject;
				// Check member access. Don't allow reflective access to
				// non-public members. Note that we can't call checkMemberAccess
				// because that expects exact stack depth!
				if (sm != null && !Modifier.isPublic(member.getModifiers())) {
					return null;
				}
				name = member.getDeclaringClass().getName();
			}
			// Now, make sure that no ClassShutter prevented Class or Member
			// of it is accessed reflectively. Note that ClassShutter may
			// prevent access to a class, even though SecurityManager permit.
			if (name != null) {
				if (!classShutter.visibleToScripts(name)) {
					return null;
				} else {
					return super.wrapAsJavaObject(cx, scope, javaObject,
							staticType);
				}
			}
		}

		// we have got some non-reflective object.
		Class<?> dynamicType = javaObject.getClass();
		String name = dynamicType.getName();
		if (!classShutter.visibleToScripts(name)) {
			// Object of some sensitive class (such as sun.net.www.*
			// objects returned from public method of java.net.URL class.
			// We expose this object as though it is an object of some
			// super class that is safe for access.

			Class<?> type = null;

			// Whenever a Java Object is wrapped, we are passed with a
			// staticType which is the type found from environment. For
			// example, method return type known from signature. The dynamic
			// type would be the actual Class of the actual returned object.
			// If the staticType is an interface, we just use that type.
			if (staticType != null && staticType.isInterface()) {
				type = staticType;
			} else {
				// dynamicType is always a class type and never an interface.
				// find an accessible super class of the dynamic type.
				while (dynamicType != null) {
					dynamicType = dynamicType.getSuperclass();
					name = dynamicType.getName();
					if (classShutter.visibleToScripts(name)) {
						type = dynamicType;
						break;
					}
				}
				// atleast java.lang.Object has to be accessible. So, when
				// we reach here, type variable should not be null.
				assert type != null : "even java.lang.Object is not accessible?";
			}
			// create custom wrapper with the 'safe' type.
			return new RhinoJavaObject(scope, javaObject, type);
		} else {
			return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
		}
	}
}
